package com.example.micro.netty.server.nio.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * NIO原理及部分源码的解析：https://www.cnblogs.com/fatmanhappycode/p/12355316.html
 * NIO为什么SelectionKey在被轮询后需要remove()：https://blog.csdn.net/qq_34866024/article/details/83184200
 * 浅谈NIO和Epoll的实现原理：https://blog.csdn.net/xing317521/article/details/105054661/
 */
public class NIOServerDemo1 {

    public static void main(String[] args) throws IOException {

        //创建ServerSocketChannel -> ServerSocket
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //得到一个Selector对象
        Selector selector = Selector.open();

        //绑定一个端口，在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);

        //把serverSocketChannel 注册到 Selector  关心事件为 OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("ServerSocketChannel注册后，注册的selectionKey数量=" + selector.keys().size());

        //循环等待客户端连接
        while (true) {

            // 获取可用channel数量, 1000 表示只阻塞1s
            int readyChannels = selector.select(1000);
            if (readyChannels == 0) {
                //这里等待1s,如果没有事件发生
                System.out.println("服务器等待了1s，无连接");
                continue;
            }

            //如果返回的>0,就获取到相关的 selectionKey集合
            //1.如果返回的>0,表示已经获取到的事件
            //2.selector.selectedKeys() 返回关注事件的集合
            //   通过selectionKeys  反向获取通道
            Set<SelectionKey> selectionKeySet = selector.selectedKeys();

            Iterator<SelectionKey> keyIterable = selectionKeySet.iterator();

            while (keyIterable.hasNext()) {
                //获取到SelectionKey
                SelectionKey key = keyIterable.next();
                //根据key 对应的通道发生的事件做相应处理
                if (key.isAcceptable()) {
                    //如果是OP_ACCEPT  即有新的客户端连接
                    //客户端生成一个SocketChannel,虽然accept是阻塞的，但是我们此时是事件驱动的，即key.isAcceptable()=true,说明此时不会阻塞
                    SocketChannel socketChannel = serverSocketChannel.accept();

                    //将socketChannel先设置为非阻塞的 再注册到selector,关注事件为OP_READ,同时给socketChannel关联一个Buffer
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));

                    System.out.println("客户端连接后，注册的selectionKey数量=" + selector.keys().size());
                }

                //发生OP_READ 如果客户端是一个新的连接，那么会在本次keyIterable轮训结束后，第二次轮训发现读事件
                if (key.isReadable()) {
                    //通过key 反向获取到对应的Channel
                    SocketChannel socketChannel = (SocketChannel) key.channel();

                    //获取到读channel关联的buffer
                    ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                    socketChannel.read(byteBuffer);
                    System.out.println(
                            "from 客户端（" + socketChannel.hashCode() + "）：" + new String(byteBuffer.array()));
                }

                //手动从集合中移除当前的SelectionKey,防止重复操作
                //本次删除的只是就绪队列中的selectionKey，并非将selecor中selectionKey删除
                keyIterable.remove();

            }

        }

    }

}
